# -*- coding: utf-8 -*-            
# @Author : HJH
# @Time : 2024/7/30 14:24
# @Use :
# 同程政策上传接口

"""
    ru_data_res 数据源
    ru_param    参数配置
"""
import base64
import copy
import gzip
import hashlib
import json
import time
import traceback
import uuid
from datetime import datetime

import pandas as pd
import requests
from loguru import logger

from entity.tc_special_policy_request import TCPolicy


def send_tongc_request(xml_params, url_path):
    url = f"http://servicegw.ly.com/gateway/supplierapi/product/{url_path}"
    headers = {
        "Labrador-Token": "1e4a230d-8ec7-46e6-876d-6eb81f65f492",
        "Content-Type": "application/json"
    }

    """
        请求接口
        """
    max_tries = 5  # 最大尝试次数
    num_tries = 0  # 已尝试次数
    sleep_time2 = 3  # 设置等待时间
    while num_tries < max_tries:
        try:
            response = requests.post(url, headers=headers, data=json.dumps(xml_params))
            logger.info("【同程请求结果】: {}", response.text)
            if response.status_code != 200:
                num_tries += 1
                if num_tries >= max_tries:
                    # 如果已经尝试到最大次数，跳出循环，放弃重试
                    logger.error(f"重试{max_tries}次后仍然失败，放弃重试。")
                    return
                continue
            # 成功请求到数据，直接返回
            return
        except requests.exceptions.RequestException as e:
            logger.error(e)
            num_tries += 1
            if num_tries >= max_tries:
                # 如果已经尝试到最大次数，跳出循环，放弃重试
                logger.error(f"重试{max_tries}次后仍然失败，放弃重试。")
                return
            # 打印日志，并且睡眠
            logger.warning(f"请求失败，休眠 {sleep_time2} 秒后进行第 {num_tries + 1} 次尝试。")
            time.sleep(sleep_time2)


def generate_md5(username, request_time_stamp):
    data = f"{username}#cgoyl@tcjp#{request_time_stamp}".encode('utf-8')
    md5_hash = hashlib.md5()
    md5_hash.update(data)
    return md5_hash.hexdigest().upper()


def add_tongcheng_policy(policies: list):
    uid = str(uuid.uuid4())
    timestamp = get_timestamp()
    sign = generate_md5('cgoyl', timestamp)
    messageHead = dict()
    messageHead['userName'] = 'cgoyl'
    messageHead['requestGuid'] = uid
    messageHead['requestTimeStamp'] = timestamp
    messageHead['signature'] = sign
    tcQueryparam = dict()
    p = compress(json.dumps(policies))
    # print("json.dumps(policies)-----" + json.dumps(policies))
    tcQueryparam['messageHead'] = messageHead
    tcQueryparam['messageBody'] = p
    return tcQueryparam


# //类型：Number  可有字段  备注：0:默认 1：按ID删除 2：按政策代码删除 3：按类型全量删除 4：按政策代码扩展字段删除 传0时，则policyIds和policyCodes节点只能传一个；传1时，
# policyIds不可为空；传2时，policyCodes节点不可为空；传3时，则policyIds和policyCodes，policyCodeExtends不需传；传4时，policyCodeExtends不可为空
def del_tc_policy(del_p_c, del_p_list):
    """
    单程统一政策删除参数
    :return:
    """
    policy_code = del_p_c
    uid = str(uuid.uuid4())
    timestamp = get_timestamp()
    sign = generate_md5('cgoyl', timestamp)
    messageHead = dict()
    messageHead['userName'] = 'cgoyl'
    messageHead['requestGuid'] = uid
    messageHead['requestTimeStamp'] = timestamp
    messageHead['signature'] = sign

    messageBody = dict()
    if del_p_list is not None:
        policy_codes = []
        for del_p in del_p_list:
            # 供应商自定义政策代码
            code = policy_code
            code = f"{code}_{del_p.get('id')[-8:]}"
            policy_codes.append(code)
        # 根据政策代码删除
        messageBody['policyCodes'] = policy_codes
        messageBody['delType'] = 2
    else:
        # 根据政策代码删除
        messageBody['policyCodes'] = [del_p_c]
        messageBody['delType'] = 2
        # 备注：code是否模糊匹配 默认否，1 否 2 是
        messageBody['isFuzzyMatch'] = 2

    tcQueryparam = dict()
    p = compress(json.dumps(messageBody))
    tcQueryparam['messageHead'] = messageHead
    tcQueryparam['messageBody'] = p
    return tcQueryparam


def compress(data):
    try:
        compressed_data = gzip.compress(data.encode('utf-8'))
        encoded_data = base64.b64encode(compressed_data)
        return encoded_data.decode('utf-8')
    except Exception as e:
        print(e)
        return ""


def compress_and_base64_encode(input_string):
    # 压缩字符串
    compressed_data = gzip.compress(input_string.encode('utf-8'))
    # 进行 Base64 编码
    encoded_data = base64.b64encode(compressed_data)
    return encoded_data.decode('utf-8')


def get_timestamp():
    timestamp_seconds = time.time()

    # 将秒数转换为毫秒数
    timestamp_milliseconds = int(timestamp_seconds * 1000)
    return timestamp_milliseconds


def md5_encrypt(dep, arr, flight_no, dep_date, cabin):
    text = dep + arr + flight_no + dep_date + cabin
    md5_hash = hashlib.md5()
    md5_hash.update(text.encode('utf-8'))
    return md5_hash.hexdigest()


def tongc_process_row(row, pr_param):
    f_list = []
    notFlightDate = None
    if row.get('航班号', '') != '':
        flightNo = row.get('航班号')
        carrier = row.get('航班号')[:2]
        f_list.append(flightNo)
        flightNoFit = 1
    else:
        f_list = ['CZ3912A']
        carrier = 'CZ'
        # 不适用航班日期
        notFlightDate = '2024-07-29/2024-07-29'
        flightNoFit = 2
    tc_policy = TCPolicy()
    # Number  必有字段  备注：行程类型  数值型，1-单程、2-往返 3-联程
    tc_policy.tripType = 1
    # 供应商自定义政策代码
    tc_policy.policyCode = pr_param.get('policy_code', 'TC_测试')
    if row.get('政策代码') is not None:
        tc_policy.policyCode = row.get('政策代码')
    # 政策有效,默认1 0-无效、1-有效
    tc_policy.isValid = 1
    # 是否共享航班,默认0 0-否、1-是
    tc_policy.isApplyShareFlight = 0
    # 是否出主承运 Number  可有字段  备注：是否出主承运，只有共享航班可选是 默认0， 0-否、1-是
    tc_policy.carryVerifyCabinPrice = 0
    # 是否代定位,默认0 ，0-否、1-是    Number  可有字段  备注：是否代定位,默认0 ，0-否、1-是
    tc_policy.isAgentBooking = 1 if pr_param.get('is_agent_booking') else 0
    # 代订位失败处理方式 默认0 (0-拦截下单 1-收单)
    tc_policy.agentBookFailType = 0
    # 报销类型,默认0，0-行程单、1-服务发票，共享航班出主承运必须为1
    tc_policy.reimbursementType = pr_param.get('reimbursement_type')
    # String  可有字段  备注：触发订位，需要使用的 office 号（字母+数字，6 位，比如 SZV122），字符长度 0~50，不支持特殊符号
    tc_policy.officeNo = pr_param.get('office_code')
    # String  可有字段  备注：客票解析，需要使用的 office 号（字母+数字，6 位，比如 SZV122），字符长度 0~50，不支持特殊符号
    tc_policy.analyzeTicketOfficeNo = ''
    if pr_param.get('pnr_seat_limit', 0) > 0:
        # Number  可有字段  备注：AV余位阈值，范围[1-9],AV余位小于当前指定值才会代订位
        tc_policy.seatLimit = pr_param.get('pnr_seat_limit')
    # 改签代订位余位阈值 Number  可有字段  备注：改期代订位AV余位阈值,非必传， 默认0，0:不代定位， [1-9]: av余位数小于等于该数字时代订位, 10:全部代订位
    tc_policy.rebookSeatLimit = 0
    if pr_param.get('stop_seat_limit') > 0:
        # AV大于此值可售 Number  可有字段  备注：AV大于此值可售，范围[0-9]
        tc_policy.avEffectiveSaleValue = pr_param.get('stop_seat_limit')

    # 运价规则 Number  可有字段  备注：资源规则类型 1-平台定制、 2-代理自定义 默认1
    if pr_param.get('pt_fare_rule', '') != '':
        tc_policy.resRuleType = 1
        # 类型：String  可有字段  备注：当resRuleType=1时才可以填,针对N人起订、性别、年龄等特殊限制资源，同程自定义规则id，有此类资源需要投放请联系客户经理获取
        tc_policy.fareRule = pr_param.get('pt_fare_rule')
    elif pr_param.get('card_type', '') == '身份证':
        tc_policy.resRuleType = 2
        # 证件
        tc_policy.resCardType = 11
        # # 年龄
        # tc_policy.resAgeType = 5
        # # 人数
        # tc_policy.resNumberType = 7
    # 资源适用场景
    tc_policy.resourceApplySceneList = ''
    # 联程组合限制
    tc_policy.transitConfig = ''
    # 航司
    tc_policy.airCompany = carrier
    # 主承运航司
    tc_policy.carryAirCompany = ''
    # 出发机场
    tc_policy.startPort = row.get('出发')
    # 抵达机场
    tc_policy.endPort = row.get('到达')
    # Number  可有字段  备注：航班限制类型  数值型  取值如下： 0=所有航班均适用 1=部分航班适用 2=部分航班不适用
    tc_policy.flightNoFit = flightNoFit
    # 不适用航线

    # 适用航班
    tc_policy.flightNoList = f_list
    # 适用班期
    tc_policy.weekSegmentList = [1, 2, 3, 4, 5, 6, 7]
    # 适用舱位
    c_list = row.get('舱位')
    tc_policy.cabinCodeList = row.get('舱位').split(',')
    # 舱位名称
    tc_policy.cabinName = '经济舱'
    # 是否是团队价 Number  可有字段  备注：是否是团队票 0-否，1-是；默认否
    tc_policy.IsTeamTicket = 0
    # 指定舱位 String  可有字段  备注：指定舱位，占座时的舱位代码，不指定无需填写
    tc_policy.particularCabinCode = ''
    # 航班开始日期
    tc_policy.startDate = row.get('旅行日期')
    # 航班结束日期
    tc_policy.endDate = row.get('旅行日期')
    # 销售开始日期
    tc_policy.startSellDate = datetime.now().strftime('%Y-%m-%d %H:%M')
    # 销售结束日期
    tc_policy.endSellDate = '2024-12-31 00:00'
    tc_policy.notFlightDate = notFlightDate
    # N天内 Number  可有字段  备注：N天内，默认365 0-365之间整数
    tc_policy.startSaleDay = pr_param.get('start_day')
    # N时刻开始销售 String  可有字段  备注：N时刻开始销售，默认值“00:00” 格式：HH:mm 可输入值范围00:00-23:59
    tc_policy.startSaleTime = '00:00'
    # N天外
    tc_policy.endSaleDay = pr_param.get('end_day')
    # N时刻停止销售
    tc_policy.endSaleTime = '00:00'
    # 销售适用班期
    tc_policy.saleWeekSegmentList = [1, 2, 3, 4, 5, 6, 7]
    # 销售开始时间
    tc_policy.startSaleApplyTime = "07:00"
    # 销售结束时间
    tc_policy.endSaleApplyTime = "23:00"
    # 计价类型 Number  必有字段  备注：计价类型：1-适用舱位、2-固定价格、4-基准舱位
    tc_policy.valuationType = 1
    # 运价类型 String  可有字段  备注：运价类型 非必传，默认空， 空字符串:非指定运价， "1":公布运价， "2"：私有运价， "3": 直减运价 ，"5": 官网B2C运价, 9": 官网B2B运价
    tc_policy.farePriceType = pr_param.get('farePriceType')
    # 特殊运价
    tc_policy.productType = ''
    # 固定价格
    # tc_policy.fixedTotalPrice = float(float(row.get('costPrice')) + pr_param.get('fare_price_add'))
    # tc_policy.fixedTotalPrice = 900
    # 动作 Number  可有字段  备注：2-加、3-减 、4-先减比例后加金额、5-先加比例后减金额 如valuationType为1 ，则actionType必传 如valuationType为2 ，则actionType不用传
    tc_policy.actionType = 3
    # 加减比例 Number  可有字段  备注：加减折扣，默认0.00 大于等于0数字，如10%，传10，录入系统后会自动除以100，精确到小数点后第二位 如valuationType为2，可不传
    tc_policy.addSubtractDiscount = 0
    # 加减金额 Number  可有字段  备注：加减金额，默认0 大于等于0整数 如valuationType为2，可不传
    tc_policy.addSubtractMoney = 1
    # 加减基准
    tc_policy.addSubtractBaseType = ''
    # 计价逻辑 Number  可有字段  备注：计价逻辑：1-逢一进十、2-进位到个位、3-四舍五入到十位、4-舍去个位、5-四舍五入到个位
    tc_policy.calculationLogic = 5
    # 卖价下限 Number  可有字段  备注：卖价下限
    tc_policy.sellPriceLowerLimit = 100
    # 卖价上限
    tc_policy.sellPriceUpperLimit = 9999
    # 成人返点来源 Number  可有字段  备注：成人返点来源 0-政策录入、1-官网接口 不填默认0
    tc_policy.adultRebateSource = 0
    # 成人返点比例 Number  必有字段  备注：成人政策返点比例,只支持数字输入，如10%，传10，录入系统后会自动除以100，精确到小数点后第一位
    tc_policy.adultRebatePercent = 0.0
    # 成人返点金额
    tc_policy.adultRebateMoney = 0.00
    # 儿童返点比例
    tc_policy.childRebatePercent = 0.0
    # 儿童返点金额
    tc_policy.childRebateMoney = 0
    # 竞价金额 Number  可有字段  备注：竞价金额(留钱)，只支持数字输入，精确到小数点后第二位 默认值0.00
    tc_policy.keepMoney = float(pr_param.get('keep_money', 0))
    if row.get('竞价') is not None:
        tc_policy.keepMoney = float(row.get('竞价'))
    # 竞价比例 Number  可有字段  备注： 竞价比例，默认0.00 大于等于0数字，如10%，传10，录入系统后会自动除以100，精确到小数点后第二位，可不传"
    tc_policy.keepMoneyPercent = 0.00
    # 是否验价
    tc_policy.isVerifyPrice = 1 if pr_param.get('is_pat') else 0
    if pr_param.get('is_pat') == 1:
        # String  可有字段  备注：验价office号
        tc_policy.verifyPriceOffice = pr_param.get('office_code')
        tc_policy.floatPrice = 30
    # 儿童出成人价  可有字段  备注：儿童出成人价 0-否，1-是 默认值0
    tc_policy.isChildEqAdultPrice = 0
    # 上浮
    tc_policy.floatPrice = pr_param.get('floatPrice')
    # 运价取值Number  可有字段  备注：适用舱取值，非必传，只适用于单程，0-最高值，1-最低值， 默认值为0；
    tc_policy.nfdType = 1
    # 库存类型Number  必有字段  备注：销售类型：1-AV有座销售、2-按库存销售
    tc_policy.sellType = 1
    # 备注
    tc_policy.remark = pr_param.get('remark')
    filtered_dict = tc_policy.__dict__
    return filtered_dict


def run_tc_upload(ru_data_res, ru_param):
    try:
        if not isinstance(ru_param, dict):
            RuntimeError("输入参数有误，请检查后重新启动")

        ru_tc_data_res = copy.deepcopy(ru_data_res)
        policyDtoList = []
        for item in ru_tc_data_res:
            policyDto = tongc_process_row(item, ru_param)
            policyDtoList.append(policyDto)

        size = 10000
        total = len(policyDtoList)
        logger.info(f"【上传TC政策数据长度】: {total}")
        if total == 0:
            return
        logger.info(f"【同程特殊政策新增上传】: 请求中... ...")
        for i in range(0, total, size):
            # 取出最大10000条数据，添加到结果列表
            end = i + size
            subset = policyDtoList[i:end]
            add_req = add_tongcheng_policy(subset)
            send_tongc_request(add_req, '/specialpolicy/addspecialpolicy/')
            logger.info(f"上传TC成功: {end}/{total}")

        logger.info(f"【同程特殊政策新增上传】成功: {ru_param.get('policy_code')}")
    except Exception as e:
        logger.error(f"【同程特殊政策新增上传】异常: {e}\n{traceback.format_exc()}")


if __name__ == '__main__':
    is_upload = 1
    policy_code = "TC_HUTP_"

    if is_upload == 1:
        ru_param = {
            # "policy_code": "TC_HGCZ_FB",  # 政策代码
            "is_agent_booking": 1,  # 是否代订位
            "reimbursement_type": 1,  # 报销类型
            "office_code": "SHA220",  # 触发订位，需要使用的 office 号
            "pnr_seat_limit": 0,  # AV余位阈值定位
            "stop_seat_limit": 4,  # AV余位阈值停售
            "farePriceType": "2",  # 运价类型 非必传，默认空， 空字符串:非指定运价， "1":公布运价， "2"：私有运价， "3": 直减运价 ，"5": 官网B2C运价, 9": 官网B2B运价
            # "pt_fare_rule": "",  # 运价规则
            # "card_type": "",  # 是否限制身份证
            "start_day": 365,  # N天内
            "end_day": 2,  # N天外
            "remark": "对比出最低价  非标可以降舱降价",  # 备注
            "is_pat": 1,  # 是否验价
            "floatPrice": 20,  # 浮动金额
        }

        df = pd.read_excel(r'./docs/cz/易宝CZ-优惠券.xlsx')

        # 将合并后的DataFrame转换为字典列表
        dict_list = df.to_dict(orient='records')

        run_tc_upload(dict_list, ru_param)
    else:
        del_tc_req = del_tc_policy(policy_code, None)
        logger.info("【同程特殊政策删除政策】: 请求中... ...")
        send_tongc_request(del_tc_req, '/specialpolicy/specialpolicydelete/')
        logger.info(f"【同程特殊政策删除成功】: {policy_code}")
